<?php
/**
 * CDbDriver class file.
 * PDO连接数据库.
 * @author Huan Song <iphper@yeah.net>
 * @version $Id: CDbDriver.php 1 2011-05-26 17:25:06Z huan.song $
 * @package system.db
 * @since 2.0
 */
class CDbDriver extends CApplication
{
	/**
	 * @var Object PDO instance.
	 */
	private $_pdo;
	
	/**
	 * @var boolean 连接数据库状态.
	 */
	private $_active = false;
	
	/**
	 * @var 连接数据库后, 紧接着执行的Sql.
	 */
	private $_initSQLs;
	
	/**
	 * 获取连接数据库的状态.
	 * <pre>
	 * $CDbDriver = new CDbDriver();
	 * $active = $CDbDriver->getActive();
	 * $active = $CDbDriver->active;
	 * </pre>
     * @return boolean 连接数据库的状态.
     * @see setActive
     */
	public function getActive()
	{
		return $this->_active;
	}
	
	/**
     * 设置连接数据库状态.
     * <pre>
	 * $CDbDriver = new CDbDriver();
	 * $CDbDriver->setActive(true);
	 * $CDbDriver->active = false;
	 * </pre>
     * @param boolean $value 状态值.
     * @see getActive
     */
	public function setActive($value)
	{
		if ($this->_active != $value)
		{
			if ($value)
				$this->_open();
			else 
				$this->_close();
		}
	}
	
	/**
     * 初始化PDO, 连接数据库.
     * @throws CDbException. 如果连接数据库失败, 抛出异常.
     * @see _close
     */
	protected function _open()
	{
		if ($this->_pdo instanceof PDO)
			return ;
		
		try
		{
			$this->_pdo = new PDO(CDbConfig::getDsn(), CDbConfig::getUsername(), CDbConfig::getPassword());
			$this->_pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
			$this->_pdo->setAttribute(PDO::ATTR_CASE, CDbConfig::getCaseFolding());
			if (null !== CDbConfig::getCharset())
			{
				$driver = strtolower($this->_pdo->getAttribute(PDO::ATTR_DRIVER_NAME));
				if (in_array($driver, array('pgsql', 'mysql', 'mysqli')))
					$this->_pdo->exec('SET NAMES ' . $this->_pdo->quote(CDbConfig::getCharset()));
			}
			
			if (is_array($this->_initSQLs))
			{
				foreach($this->_initSQLs as $sql)
					$this->_pdo->exec($sql);
			}
			
			$this->_active = true;
		}
		catch(PDOException $e)
		{
			throw new CDbException('CDbDriver:open failed to open the DB connection', 
				array('{code}' => (int) $e->getCode(), '{errorInfo}' => $e->errorInfo, '{message}' => $e->getMessage()));
		}
	}
	
	/**
     * 初始化PDO, 关闭数据库.
     * @see _open
     */
	protected function _close()
	{
		$this->_pdo = null;
		$this->_active = false;
	}
	
	/**
	 * 获取PDO实例化对象.
	 * <pre>
	 * $CDbDriver = new CDbDriver();
	 * $pdo = $CDbDriver->getPdo();
	 * $pdo = $CDbDriver->pdo;
	 * </pre>
     * @return Object PDO instance.
     */
	public function getPdo()
	{
		return $this->_pdo;
	}
	
	/**
	 * 获取最后一次插入的ID.
	 * <pre>
	 * $CDbDriver = new CDbDriver();
	 * $pdo = $CDbDriver->getLastInsertId();
	 * $pdo = $CDbDriver->lastInsertId;
	 * </pre>
     * @return integer 最后一次插入的ID.
     * @throws CDbException. 如果失败取不到最后一次插入的ID, 抛出异常.
     */
	public function getLastInsertId()
    {
		try 
		{
			return (int) $this->_pdo->lastInsertId();
		}
		catch (PDOException $e)
		{
			throw new CDbException('CDbDriver:lastInsertId failed to get last id', 
			array('{code}' => (int) $e->getCode(), '{errorInfo}' => $e->errorInfo, '{message}' => $e->getMessage()));
		}
    }
	
    /**
     * 防止SQL注入, 对字符串进行处理.
     * <pre>
	 * $CDbDriver = new CDbDriver();
	 * $str = $CDbDriver->quoteValue('"\a');
	 * 结果: \"\\a
	 * 
	 * $str = $CDbDriver->quoteValue("'\a");
	 * 结果: \'\\a
	 * </pre>
     * @param string $str 普通的字符串.
     * @return string 处理后的字符串.
     */
	public function quoteValue($str)
	{
		if (is_int($str) || is_float($str))
			return $str;
		
		if ($this->_active && false !== ($value = $this->_pdo->quote($str)))
			return $value;
		else  // the driver doesn't support quote (e.g. oci)
			return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'";
	}
	
	/**
	 * 将普通的字符类型转化成PDO的字符类型.
	 * <pre>
	 * $CDbDriver = new CDbDriver();
	 * $type = $CDbDriver->getPdoType(getType(2));
	 * 结果: PDO::PARAM_INT(1)
	 * 
	 * $type = $CDbDriver->getPdoType(getType('a'));
	 * 结果: PDO::PARAM_STR(2)
	 * 
	 * $type = $CDbDriver->getPdoType(getType(true));
	 * 结果: PDO::PARAM_BOOL(5)
	 * 
	 * $type = $CDbDriver->getPdoType(getType(NULL));
	 * 结果: PDO::PARAM_NULL(0)
	 * </pre>
	 * @param string $type 普通的字符类型.
     * @return integer PDO的字符类型.
     */
	public function getPdoType($type)
	{
		static $map = array
		(
			'boolean' => PDO::PARAM_BOOL,
			'integer' => PDO::PARAM_INT,
			'string' => PDO::PARAM_STR,
			'NULL' => PDO::PARAM_NULL
		);
		return isset($map[$type]) ? $map[$type] : PDO::PARAM_STR;
	}
	
	/**
	 * 获取通过列名取值时, 列名的格式.
	 * <pre>
	 * $CDbDriver = new CDbDriver();
	 * $caseFolding = $CDbDriver->getCaseFolding();
	 * $caseFolding = $CDbDriver->caseFolding;
	 * </pre>
     * @return integer 通过列名取值时, 列名的格式.
     * @see setCaseFolding
     */
	public function getCaseFolding()
	{
		return $this->getAttribute(PDO::ATTR_CASE);
	}
	
	/**
	 * 设置通过列名取值时, 列名的格式.
     * @param integer $value 列名的格式.
     * @throws CDbException. 如果列名的格式不正确, 抛出异常.
     * @see getCaseFolding
     */
	public function setCaseFolding($value = PDO::CASE_NATURAL)
    {
    	if (!in_array($value, array(PDO::CASE_NATURAL, PDO::CASE_LOWER, PDO::CASE_UPPER)))
    		throw new CDbException('CDbDriver:setCaseFolding invalid caseFolding', 
					array('{caseFolding}' => $value));
		
		$this->setAttribute(PDO::ATTR_CASE, $value);
    }
	
    /**
     * 获取自动提交的属性.
     * <pre>
	 * $CDbDriver = new CDbDriver();
	 * $caseFolding = $CDbDriver->getAutoCommit();
	 * $caseFolding = $CDbDriver->autoCommit;
	 * </pre>
     * @return integer 自动提交的属性.
     * @see setAutoCommit
     */
	public function getAutoCommit()
	{
		return $this->getAttribute(PDO::ATTR_AUTOCOMMIT);
	}
	
	/**
	 * 设置自动提交的属性.
     * @param integer $value 自动提交的属性.
     * @see getAutoCommit
     */
	public function setAutoCommit($value)
	{
		$this->setAttribute(PDO::ATTR_AUTOCOMMIT, $value);
	}
	
	/**
	 * 通过键名, 获取PDO的属性.
	 * <pre>
	 * $CDbDriver = new CDbDriver();
	 * $attribute = $CDbDriver->getAttribute(PDO::ATTR_AUTOCOMMIT);
	 * </pre>
     * @param string $name 键名.
     * @return integer 属性.
     * @throws CDbException. 如果无法获取属性, 抛出异常.
     * @see setAttribute
     */
	public function getAttribute($name)
	{
	    try
    	{
            return $this->_pdo->getAttribute($name);
    	}
		catch (PDOException $e)
		{
            throw new CDbException('CDbDriver:getAttribute failed to get pdo attribute', 
				array('{name}' => $name, '{errorInfo}' => $e->errorInfo, '{message}' => $e->getMessage()));
		}
	}
	
	/**
	 * 设置获取PDO的属性.
     * @param string $name 键名.
     * @param integer $value 属性.
     * @throws CDbException. 如果无法设置属性, 抛出异常.
     * @see getAttribute
     */
	public function setAttribute($name, $value)
	{
		try
    	{
        	$this->_pdo->setAttribute($name, $value);
    	}
		catch (PDOException $e)
		{
            throw new CDbException('CDbDriver:setAttribute failed to set pdo attribute', 
				array('{name}' => $name, '{value}' => $value, '{errorInfo}' => $e->errorInfo, '{message}' => $e->getMessage()));
		}
	}
	
	/**
	 * 开始PDO事务.
	 * @throws CDbException. 如果开启失败, 抛出异常.
     */
	public function beginTransaction()
    {
    	try
    	{
        	$this->_pdo->beginTransaction();
    	}
		catch (PDOException $e)
		{
            throw new CDbException('CDbDriver:beginTransaction failed to beginTransaction', 
				array('{code}' => (int) $e->getCode(), '{errorInfo}' => $e->errorInfo, '{message}' => $e->getMessage()));
		}
    }
	
    /**
	 * 回滚PDO事务.
	 * @throws CDbException. 如果回滚失败, 抛出异常.
     */
    public function rollBack()
    {
        try
    	{
        	$this->_pdo->rollBack();
    	}
		catch (PDOException $e)
		{
            throw new CDbException('CDbDriver:rollBack failed to rollBack', 
				array('{code}' => (int) $e->getCode(), '{errorInfo}' => $e->errorInfo, '{message}' => $e->getMessage()));
		}
    }
	
    /**
	 * 提交PDO事务.
	 * @throws CDbException. 如果提交失败, 抛出异常.
     */
    public function commit()
    {
        try
    	{
        	$this->_pdo->commit();
    	}
		catch (PDOException $e)
		{
            throw new CDbException('CDbDriver:commit failed to commit', 
				array('{code}' => (int) $e->getCode(), '{errorInfo}' => $e->errorInfo, '{message}' => $e->getMessage()));
		}
    }
}
